/*
 * File     : StructureHelper.java
 * Created  : 19 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.structure;

import static com.googlecode.dni.internal.DniInternal.LOGGER;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.util.logging.Level;

import com.googlecode.dni.type.Pointer;


/**
 * <p>
 *  Provides helper methods for structure implementations.
 * </p>
 *
 * @author Matthew Wilson
 */
public final class StructureHelper
{

    /** Prevents instantiation. */
    private StructureHelper()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @param a
     *            the first object
     * @param b
     *            the second object
     * @return <code>true</code> iff the objects are equal
     */
    public static boolean equals( final Object a, final Object b )
    {
        if ( a == b )
        {
            return true;
        }

        return a == null ? false : a.equals( b );
    }

    /**
     * @param a
     *            the first value
     * @param b
     *            the second value
     * @return <code>true</code> iff the values are equal
     */
    public static boolean equals( final float a, final float b )
    {
        return Float.floatToRawIntBits( a ) == Float.floatToRawIntBits( b );
    }

    /**
     * @param a
     *            the first value
     * @param b
     *            the second value
     * @return <code>true</code> iff the values are equal
     */
    public static boolean equals( final double a, final double b )
    {
        return Double.doubleToRawLongBits( a ) == Double.doubleToRawLongBits( b );
    }

    /**
     * @param object
     *            the object
     * @return a hash-code
     */
    public static int hashCode( final Object object )
    {
        return object != null ? object.hashCode() : 0;
    }

    /**
     * @param value
     *            the value
     * @return a hash-code
     */
    public static int hashCode( final long value )
    {
        return ( (int) value ) ^ ( (int) ( value >> 32 ) );
    }

    /**
     * @param value
     *            the value
     * @return a hash-code
     */
    public static int hashCode( final float value )
    {
        return Float.floatToRawIntBits( value );
    }

    /**
     * @param value
     *            the value
     * @return a hash-code
     */
    public static int hashCode( final double value )
    {
        return hashCode( Double.doubleToRawLongBits( value ) );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final ByteBuffer buffer, final long pointer )
    {
        long offset = pointer - Pointer.fromBuffer( buffer ).getAddress();
        if ( offset < 0 || offset > buffer.limit() )
        {
            return false;
        }

        buffer.position( (int) offset );
        return true;
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final ShortBuffer buffer, final long pointer )
    {
        return updateBuffer( buffer, pointer, 2 );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final CharBuffer buffer, final long pointer )
    {
        return updateBuffer( buffer, pointer, 2 );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final IntBuffer buffer, final long pointer )
    {
        return updateBuffer( buffer, pointer, 4 );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final LongBuffer buffer, final long pointer )
    {
        return updateBuffer( buffer, pointer, 8 );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final FloatBuffer buffer, final long pointer )
    {
        return updateBuffer( buffer, pointer, 4 );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @return <code>true</code> iff updated
     */
    public static boolean updateBuffer( final DoubleBuffer buffer, final long pointer )
    {
        return updateBuffer( buffer, pointer, 8 );
    }

    /**
     * Handles an exception thrown in a finalizer of a structure.
     *
     * @param structure
     *            the structure
     * @param throwable
     *            the throwable
     */
    public static void handleFinalizerThrowable( final Object structure,
                                                 final Throwable throwable )
    {
        LOGGER.log( Level.WARNING, "Failed to finalize structure: " + structure, throwable );
    }

    /**
     * Updates a buffer according to the rules for structures.
     *
     * @param buffer
     *            the buffer
     * @param pointer
     *            the pointer
     * @param unitSize
     *            the primitive unit size
     * @return <code>true</code> iff updated
     */
    private static boolean updateBuffer( final Buffer buffer, final long pointer, final int unitSize )
    {
        long offset = pointer - Pointer.fromBuffer( buffer ).getAddress();
        if ( offset < 0 || offset > buffer.limit() * unitSize || ( offset % unitSize ) != 0 )
        {
            return false;
        }

        buffer.position( (int) ( offset / unitSize ) );
        return true;
    }

}
